<!-- src/components/WaveBackground.vue -->
<template>
    <div ref="holder" class="wave-holder">
      <canvas ref="canvas" class="wave-canvas"></canvas>
    </div>
  </template>
  
  <script setup>
  import { ref, onMounted, onUnmounted } from 'vue'
  
  const holder = ref(null)
  const canvas = ref(null)
  let animationFrame = null
  
  const PI = Math.PI
  const PI2 = 2 * Math.PI
  
  class WaveAnimation {
    constructor(holder, options) {
      this.options = this.extend(options || {}, {
        resize: true,
        rotation: 45,
        waves: 3,
        width: 200,
        hue: [11, 14],
        amplitude: 0.5,
        background: true,
        speed: [0.004, 0.008],
        debug: false
      })
  
      this.waves = []
      this.holder = holder.value
      this.canvas = canvas.value
      this.ctx = this.canvas.getContext('2d')
      this.holder.appendChild(this.canvas)
  
      this.hue = this.options.hue[0]
      this.hueFw = true
  
      this.resize()
      this.init()
    }
  
    init() {
      for (let i = 0; i < this.options.waves; i++) {
        this.waves[i] = new Wave(this)
      }
    }
  
    render() {
      this.updateColor()
      this.clear()
  
      if (this.options.background) {
        this.background()
      }
  
      this.waves.forEach(wave => {
        wave.update()
        wave.draw()
      })
    }
  
    animate() {
      this.render()
      animationFrame = requestAnimationFrame(this.animate.bind(this))
    }
  
    clear() {
      this.ctx.clearRect(0, 0, this.width, this.height)
    }
  
    background() {
      const gradient = this.ctx.createLinearGradient(0, 0, 0, this.height)
      gradient.addColorStop(0, '#000')
      gradient.addColorStop(1, this.color)
  
      this.ctx.fillStyle = gradient
      this.ctx.fillRect(0, 0, this.width, this.height)
    }
  
    resize() {
      const width = this.holder.offsetWidth
      const height = this.holder.offsetHeight
      this.scale = window.devicePixelRatio || 1
      this.width = width * this.scale
      this.height = height * this.scale
      this.canvas.width = this.width
      this.canvas.height = this.height
      this.canvas.style.width = width + 'px'
      this.canvas.style.height = height + 'px'
      this.radius = Math.sqrt(Math.pow(this.width, 2) + Math.pow(this.height, 2)) / 2
      this.centerX = this.width / 2
      this.centerY = this.height / 2
    }
  
    updateColor() {
      this.hue += (this.hueFw) ? 0.01 : -0.01
  
      if (this.hue > this.options.hue[1] && this.hueFw) {
        this.hue = this.options.hue[1]
        this.hueFw = false
      } else if (this.hue < this.options.hue[0] && !this.hueFw) {
        this.hue = this.options.hue[0]
        this.hueFw = true
      }
  
      const a = Math.floor(127 * Math.sin(0.3 * this.hue + 0) + 128)
      const b = Math.floor(127 * Math.sin(0.3 * this.hue + 2) + 128)
      const c = Math.floor(127 * Math.sin(0.3 * this.hue + 4) + 128)
  
      this.color = `rgba(${a},${b},${c},0.1)`
    }
  
    extend(options, defaults) {
      for (let key in options) {
        if (defaults.hasOwnProperty(key)) {
          defaults[key] = options[key]
        }
      }
      return defaults
    }
  }
  
  class Wave {
    constructor(waves) {
      this.waves = waves
      this.lines = []
      this.angle = [
        this.rnd(PI2),
        this.rnd(PI2),
        this.rnd(PI2),
        this.rnd(PI2)
      ]
  
      const speed = waves.options.speed
      this.speed = [
        this.rnd(speed[0], speed[1]) * this.rndSign(),
        this.rnd(speed[0], speed[1]) * this.rndSign(),
        this.rnd(speed[0], speed[1]) * this.rndSign(),
        this.rnd(speed[0], speed[1]) * this.rndSign()
      ]
    }
  
    update() {
      this.lines.push(new Line(this, this.waves.color))
      if (this.lines.length > this.waves.options.width) {
        this.lines.shift()
      }
    }
  
    draw() {
      const ctx = this.waves.ctx
      const radius = this.waves.radius
      const radius3 = radius / 3
      const x = this.waves.centerX
      const y = this.waves.centerY
      const rotation = this.dtr(this.waves.options.rotation)
      const amplitude = this.waves.options.amplitude
  
      this.lines.forEach(line => {
        const angle = line.angle
  
        const x1 = x - radius * Math.cos(angle[0] * amplitude + rotation)
        const y1 = y - radius * Math.sin(angle[0] * amplitude + rotation)
        const x2 = x + radius * Math.cos(angle[3] * amplitude + rotation)
        const y2 = y + radius * Math.sin(angle[3] * amplitude + rotation)
        const cpx1 = x - radius3 * Math.cos(angle[1] * amplitude * 2)
        const cpy1 = y - radius3 * Math.sin(angle[1] * amplitude * 2)
        const cpx2 = x + radius3 * Math.cos(angle[2] * amplitude * 2)
        const cpy2 = y + radius3 * Math.sin(angle[2] * amplitude * 2)
  
        ctx.strokeStyle = line.color
        ctx.beginPath()
        ctx.moveTo(x1, y1)
        ctx.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, x2, y2)
        ctx.stroke()
      })
    }
  
    dtr(deg) {
      return deg * PI / 180
    }
  
    rnd(a, b) {
      if (arguments.length === 1) return Math.random() * a
      return a + Math.random() * (b - a)
    }
  
    rndSign() {
      return (Math.random() > 0.5) ? 1 : -1
    }
  }
  
  class Line {
    constructor(wave, color) {
      const angle = wave.angle
      const speed = wave.speed
  
      this.angle = [
        Math.sin(angle[0] += speed[0]),
        Math.sin(angle[1] += speed[1]),
        Math.sin(angle[2] += speed[2]),
        Math.sin(angle[3] += speed[3])
      ]
  
      this.color = color
    }
  }
  
  onMounted(() => {
    const waveAnimation = new WaveAnimation(holder, {
      waves: 3,
      width: 200
    })
    waveAnimation.animate()
  
    const handleResize = () => {
      waveAnimation.resize()
    }
  
    window.addEventListener('resize', handleResize)
  
    onUnmounted(() => {
      cancelAnimationFrame(animationFrame)
      window.removeEventListener('resize', handleResize)
    })
  })
  </script>
  
  <style scoped>
  .wave-holder {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 1;
  }
  
  .wave-canvas {
    display: block;
    width: 100%;
    height: 100%;
  }
  </style>